﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using Microsoft.Win32;

namespace XiphMediaEncoder
{
   #region Enums

   public enum AfterEncodingAction
   {
      DoNothing = 0,
      Shutdown = 1,
      Hibernate = 2
   }
   
   public enum WizardStep
   {
      SetupPowershell,
      Task,
      Encoding,
      Decoding,
      Transcoding,
      Input,
      Output,
      Process
   }
   
   public enum ConversionType
   {
      Undefined,
      
      // Encoding
      PcmToFlac,
      PcmToVorbis,
      PcmToSpeex,
      
      // Decoding
      FlacToPcm,
      VorbisToPcm,
      SpeexToPcm,
      
      // Transcoding
      FlacToFlac,
      FlacToVorbis
   }
   
   #endregion
   
   public class ScriptGenerator
   {
      public ConversionType Conversion { get; set; }
      public string SourcePath { get; private set; }
      public bool Recursive { get; private set; }
      public string InputFilenamePattern { get; private set; }
      public string InputFolderPattern { get; private set; }
      public bool CanRunScript { get; private set; }
      public string OutputFilenamePattern { get; private set; }
      public string OutputFolderPattern { get; private set; }
      public int Quality { get; private set; }
      public int MaxParallelEncoding { get; private set; }
      public AfterEncodingAction AfterEncoding { get; private set; }
      public string ScriptPath { get; private set; }
      public string Priority { get; private set; }

      private readonly string rootFolder;
      private readonly string rootTemplate;

      public ScriptGenerator()
      {
         rootFolder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
         rootTemplate = Path.Combine(rootFolder, "Templates");
      }
      
      public string InputFileType
      {
         get
         {
            switch (Conversion) 
            {
               case ConversionType.PcmToFlac:
               case ConversionType.PcmToVorbis:
               case ConversionType.PcmToSpeex:
                  return "Wave PCM (*.wav)";
               case ConversionType.FlacToPcm:
               case ConversionType.FlacToFlac:
               case ConversionType.FlacToVorbis:
                  return "FLAC (*.flac; *.ogg)";
               case ConversionType.VorbisToPcm:
                  return "Ogg Vorbis (*.ogg)";
               case ConversionType.SpeexToPcm:
                  return "Speex (*.spx; *.ogg)";
            }
            
             return null;
         }
      }
      
      public string OutputFileType
      {
         get
         {
            switch (Conversion) 
            {
               case ConversionType.PcmToFlac:
               case ConversionType.FlacToFlac:
                  return "FLAC (*.flac)";
               case ConversionType.PcmToVorbis:
               case ConversionType.FlacToVorbis:
                  return "Ogg Vorbis (*.ogg)";
               case ConversionType.PcmToSpeex:
                  return "Speex (*.spx)";
               case ConversionType.FlacToPcm:
               case ConversionType.VorbisToPcm:
               case ConversionType.SpeexToPcm:
                  return "PCM Wave (*.wav)";
            }
            
             return null;
         }
      }
      
      public string[] GetInputFiles(string source, bool recursive)
      {
         string fileType = InputFileType;
         if (fileType == null)
            throw new InvalidOperationException();
         
         int i = fileType.IndexOf('(') + 1;
         var exts = fileType.Substring(i, fileType.Length - i - 1).Split(';').Select(s => s.Trim()).ToArray();
         
         var files = new List<string>();
         foreach (string ext in exts)
            files.AddRange(Directory.EnumerateFiles(source, ext, recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly));
         
         return files.ToArray();
      }
      
      public void CheckPrerequist()
      {
         bool isVistaOrLater = Environment.OSVersion.Platform == PlatformID.Win32NT && Environment.OSVersion.Version.Major >= 6;
         if (!isVistaOrLater)
            throw new PlatformNotSupportedException("You must have Windows Vista or later to use the Xiph Media Encoder GUI.");

         object o = Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\PowerShell\1", "Install", 0);
         bool powerShellInstalled = o != null && (int)o == 1;
         if (!powerShellInstalled)
            throw new NotSupportedException("You must install PowerShell before using Xiph Media Encoder.");
         
         string policy = (string)Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\PowerShell\1\ShellIds\Microsoft.PowerShell", "ExecutionPolicy", null);
         CanRunScript = policy == "RemoteSigned" || policy == "Unrestricted" || policy == "Bypass";
      }
      
      public void SetInputOptions(string source, bool recursive, string filenamePattern, string folderPattern)
      {
         SourcePath = source;
         Recursive = recursive;
         InputFilenamePattern = filenamePattern;
         InputFolderPattern = folderPattern;
      }

      public void SetOutputOptions(int quality, string filenamePattern, string folderPattern)
      {
         Quality = quality;
         OutputFilenamePattern = filenamePattern;
         OutputFolderPattern = folderPattern;
      }

      public void SetProcessOptions(int maxCPU, string cpuPriority, AfterEncodingAction afterEncoding, string scriptName)
      {
         MaxParallelEncoding = maxCPU;
         Priority = cpuPriority;
         AfterEncoding = afterEncoding;
         ScriptPath = Path.Combine(rootFolder, scriptName);
      }

      public static void RunScript(string scriptPath, bool keepWindowOpen)
      {
         var proc = new ProcessStartInfo();
         proc.UseShellExecute = true;
         proc.FileName = "powershell.exe";
         proc.Arguments = keepWindowOpen ? String.Format("-noexit &'{0}'", scriptPath) : String.Format("&'{0}'", scriptPath);
         
         Process.Start(proc);
      }

      public void SaveScript()
      {
         var sb = new StringBuilder();
         switch (Conversion)
         {
            case ConversionType.Undefined:
               throw new InvalidOperationException();
            case ConversionType.PcmToFlac:
               sb.Append(File.ReadAllText(Path.Combine(rootTemplate, "PCM2FLAC.tpl.ps1")));
               sb.Replace("%MAX_ENCODING%", MaxParallelEncoding == 0 ? "[Environment]::ProcessorCount" : MaxParallelEncoding.ToString());
               sb.Replace("%PRIORITY%", Priority);
               sb.Replace("%INPUT_FILENAME_PATTERN%", InputFilenamePattern);
               sb.Replace("%INPUT_FOLDER_PATTERN%", InputFolderPattern);
               sb.Replace("%OUTPUT_FILENAME_PATTERN%", OutputFilenamePattern);
               sb.Replace("%OUTPUT_FOLDER_PATTERN%", OutputFolderPattern);
               break;
            case ConversionType.PcmToVorbis:
               sb.Append(File.ReadAllText(Path.Combine(rootTemplate, "PCM2Vorbis.tpl.ps1")));
               sb.Replace("%MAX_ENCODING%", MaxParallelEncoding == 0 ? "[Environment]::ProcessorCount" : MaxParallelEncoding.ToString());
               sb.Replace("%PRIORITY%", Priority);
               sb.Replace("%INPUT_FILENAME_PATTERN%", InputFilenamePattern);
               sb.Replace("%INPUT_FOLDER_PATTERN%", InputFolderPattern);
               sb.Replace("%OUTPUT_FILENAME_PATTERN%", OutputFilenamePattern);
               sb.Replace("%OUTPUT_FOLDER_PATTERN%", OutputFolderPattern);
               sb.Replace("%QUALITY%", Quality.ToString());
               break;
            case ConversionType.PcmToSpeex:
               sb.Append(File.ReadAllText(Path.Combine(rootTemplate, "PCM2Speex.tpl.ps1")));
               sb.Replace("%MAX_ENCODING%", MaxParallelEncoding == 0 ? "[Environment]::ProcessorCount" : MaxParallelEncoding.ToString());
               sb.Replace("%PRIORITY%", Priority);
               sb.Replace("%INPUT_FILENAME_PATTERN%", InputFilenamePattern);
               sb.Replace("%INPUT_FOLDER_PATTERN%", InputFolderPattern);
               sb.Replace("%OUTPUT_FILENAME_PATTERN%", OutputFilenamePattern);
               sb.Replace("%OUTPUT_FOLDER_PATTERN%", OutputFolderPattern);
               sb.Replace("%QUALITY%", Quality.ToString());
               break;
            case ConversionType.FlacToPcm:
               sb.Append(File.ReadAllText(Path.Combine(rootTemplate, "FLAC2PCM.tpl.ps1")));
               sb.Replace("%OUTPUT_FILENAME_PATTERN%", OutputFilenamePattern);
               sb.Replace("%OUTPUT_FOLDER_PATTERN%", OutputFolderPattern);
               break;
            case ConversionType.VorbisToPcm:
               sb.Append(File.ReadAllText(Path.Combine(rootTemplate, "Vorbis2PCM.tpl.ps1")));
               sb.Replace("%OUTPUT_FILENAME_PATTERN%", OutputFilenamePattern);
               sb.Replace("%OUTPUT_FOLDER_PATTERN%", OutputFolderPattern);
               break;
            case ConversionType.SpeexToPcm:
               sb.Append(File.ReadAllText(Path.Combine(rootTemplate, "Speex2PCM.tpl.ps1")));
               sb.Replace("%OUTPUT_FILENAME_PATTERN%", OutputFilenamePattern);
               sb.Replace("%OUTPUT_FOLDER_PATTERN%", OutputFolderPattern);
               break;
            case ConversionType.FlacToFlac:
               sb.Append(File.ReadAllText(Path.Combine(rootTemplate, "FLAC2FLAC.tpl.ps1")));
               sb.Replace("%MAX_ENCODING%", MaxParallelEncoding == 0 ? "[Environment]::ProcessorCount" : MaxParallelEncoding.ToString());
               sb.Replace("%PRIORITY%", Priority);
               break;
            case ConversionType.FlacToVorbis:
               sb.Append(File.ReadAllText(Path.Combine(rootTemplate, "FLAC2Vorbis.tpl.ps1")));
               sb.Replace("%MAX_ENCODING%", MaxParallelEncoding == 0 ? "[Environment]::ProcessorCount" : MaxParallelEncoding.ToString());
               sb.Replace("%PRIORITY%", Priority);
               sb.Replace("%OUTPUT_FILENAME_PATTERN%", OutputFilenamePattern);
               sb.Replace("%OUTPUT_FOLDER_PATTERN%", OutputFolderPattern);
               sb.Replace("%QUALITY%", Quality.ToString());
               break;
         }
       
         sb.Replace("%SOURCE%", SourcePath);
         sb.Replace("%RECURSIVE%", Recursive ? "$true" : "$false");
         sb.Replace("%SHUTDOWN%", AfterEncoding == AfterEncodingAction.Shutdown ? "$true" : "$false");
         sb.Replace("%HIBERNATE%", AfterEncoding == AfterEncodingAction.Hibernate ? "$true" : "$false");

         File.WriteAllText(ScriptPath, sb.ToString(), Encoding.UTF8);
      }
      
      public void SetupPowershell()
      {
         var proc = new ProcessStartInfo();
         proc.UseShellExecute = true;
         proc.WindowStyle = ProcessWindowStyle.Hidden;
         proc.FileName = "powershell.exe";
         proc.Arguments = "-command set-executionpolicy remotesigned -force";
         proc.Verb = "runas";
         
         try
         {
            Process p = Process.Start(proc);
            p.WaitForExit();
            if (p.ExitCode == 0)
               CanRunScript = true;
         }
         catch 
         {
            // elevation refused by user
         }
      }      
   }
}